# 数据加载

import torch
import torch.nn as nn
import numpy as np
# Dataset 是一个抽象类，定义数据及需要继承 Dataset
from torch.utils.data import Dataset
from torch.utils.data import DataLoader

# 在 pytorch06_multiple_dim_input 中训练时使用所有数据更新一次参数，效果不明显.这部分中使用 Mini-batch 方式进行训练.

"""
定义数据集通常需要实现3个函数，__init__() 初始化函数，__getitem__() 根据项目索引获得项目，__len__() 数据集的长度。

步骤：
    1、继承抽象类 Dataset
        1.1、实现__init__函数，__getitem__函数，__len__函数
    2、获得 Dataset 实例
    3、定义数据集加载器，获得 DataLoader 实例
    4、使用加载之后的数据训练模型
"""


# 定义数据集，继承 Dataset 类
class DiabetesDataset(Dataset):
    def __init__(self, filepath):
        # 使用numpy架子啊数据，filepath为文件路径，delimiter表示文件中各列的分隔符，指定数据类型 dtype
        xy = np.loadtxt(filepath, delimiter=',', dtype=np.float32)
        # 获得数据集的长度
        self.len = xy.shape[0]
        # 加载样本数据，但不加载最后一列
        self.x_data = torch.from_numpy(xy[:, :-1])
        # 加载标签数据，只加载数据集文件的最后一列
        self.y_data = torch.from_numpy(xy[:, [-1]])

    def __getitem__(self, index):
        # 根据索引获得样本数据和标签数据
        return self.x_data[index], self.y_data[index]

    def __len__(self):
        return self.len


dataset = DiabetesDataset('../datasets/diabetes.csv.gz')

# 指定数据集dataset=dataset，指定 batch 大小 batch_size=32，是否打乱样本顺序划分batch，shuffle=True，进程数 num_workers=2
train_loader = DataLoader(dataset=dataset,
                          batch_size=32,
                          shuffle=True,
                          num_workers=2)


class MultiDimInputModel(nn.Module):
    def __init__(self):
        """
        设置参数，并进行初始化
        """
        # 调用父类的 __init__函数
        super(MultiDimInputModel, self).__init__()
        # 定义线性模型，nn.Linear(in_features,out_features,bias) 分别是输入样本的特征维度，输出结果维度，是否使用偏执项，默认使用。
        # 定义多个线性层
        self.linear1 = nn.Linear(8, 6)
        self.linear2 = nn.Linear(6, 4)
        self.linear3 = nn.Linear(4, 1)
        # 定义一个激活函数，这里使用torch自带的 sigmoid 函数作为激活函数
        self.activate = nn.Sigmoid()

    # 定义前馈计算，forward是一个callable函数，在父类中实现，这里是对父类中 forward 函数的重载
    def forward(self, x):
        # 第一次线性变换之后使用 sigmoid 激活
        x = self.activate(self.linear1(x))
        # 第二次线性变换之后使用 sigmoid 激活
        x = self.activate(self.linear2(x))
        # 第三次线性变换之后激活，得到 y_pred
        x = self.activate(self.linear3(x))
        return x


# 得到模型
model = MultiDimInputModel()

# 定义损失函数，使用你二分类交叉熵损失
# criterion = torch.nn.BCELoss(size_average=True)
# 使用这个参数，size_average 过时
criterion = torch.nn.BCELoss(reduction='mean')
# 使用随机梯度优化算法，传入需要学习的参数和学习率
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

# 使用pytorch06中的网络模型进行训练
# 这里由于多线程加载数据在Windows喜出现的问题，需要添加语句 if __name__ == '__main__': 才能正确执行
if __name__ == '__main__':
    for epoch in range(100):
        # 获取Mini-Batch的样本数据，enumerate(train_loader, 0)返回一个元组，分别是数据的索引和数据，0表示索引从0开始
        for i, data in enumerate(train_loader, 0):
            # 1、Prepare data
            inputs, labels = data
            # 2、Forward，获得预测值
            y_pred = model(inputs)
            # 计算损失
            loss = criterion(y_pred, labels)
            print(epoch, i, loss.item())
            # 3、Backward，梯度清 0
            optimizer.zero_grad()
            # 反向传播
            loss.backward()
            # 4、Update，参数更新
            optimizer.step()
